package com.wideplay.warp.remoting;

import com.google.inject.AbstractModule;
import com.google.inject.Scope;
import com.google.inject.Singleton;

import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.Set;
import java.util.Map;
import java.util.HashMap;

/**
 * Created by IntelliJ IDEA.
 * User: dprasanna
 * Date: 1/10/2007
 * Time: 14:22:55
 * <p/>
 *
 * Extend this module as you would a Guice abstract module and feed it into Guice.createInjector().
 *
 * The binding EDSL for this module is as follows (Server-side):
 *
 * //first you must bind a "remoting" scope, which is basically a remoting profile and transport
 * bindRemoteScope(MyRmiServer.class).via(RMI).at("localhost", 1199);
 *
 * //MyRmiServer is a scoping annotation thru which you can expose your web services:
 * bindRemoted(MyService.class).to(MyServiceImpl.class).in(MyRmiServer.class);
 *
 *
 * The binding EDSL for this module is as follows (Client-side):
 *
 * //similarly to the server setup, you must first bind a "remoting" scope (usually exactly the same as the server):
 * bindRemoteScope(MyRmiClient.class).via(RMI).at("localhost", 1199);
 *
 * //notice I used a different annotation, now I can proxy my interfaces thru that scoping annotation
 * bindRouter(MyService.class).in(MyRmiClient.class);
 *
 * //and now you're ready to begin using the remote service:
 * MyService remoted = injector.getInstance(MyService.class);
 * remoted.echo();   //invoked on remote server!!
 * 
 * @author dprasanna
 * @since 1.0
 */
public abstract class AbstractRemotingModule extends AbstractModule {
    private final Set<RemoteBinding<?>> remoteBindings = new HashSet<RemoteBinding<?>>();
    private final Map<Class<? extends Annotation>, RemoteScopeDescriptor> remoteScopes =
            new HashMap<Class<? extends Annotation>, RemoteScopeDescriptor>();
    
    private final Set<RouterDescriptor> routerBindings = new HashSet<RouterDescriptor>();

    private static boolean isInjectorProviderBound = false;

    protected <T> RemoteLinkedBindingBuilder<T> bindRemoted(Class<T> iface) {
        return new RemotingModuleBuilderImpl<T>(iface, this);
    }

    protected ProtocolBindingBuilder bindRemoteScope(Class<? extends Annotation> clazz) {
        //a horrible singleton hack to expose the injector to unmanaged services
        if (!isInjectorProviderBound) {
            //bind injector provider, the moment a remote scope is registered
            bind(InjectorProvider.class).asEagerSingleton();
            isInjectorProviderBound = true;
        }
        
        return new RemoteScopeBuilderImpl(this, clazz);
    }

    protected RemoteScopeBindingBuilder bindRouter(Class<?> clazz) {


        return new RouterBuilderImpl(clazz, routerBindings);
    }


    //package local methods that actually translate the binding to guice
    void doScopeBinding(RemoteScopeDescriptor remoteScopeDescriptor) {
        Scope scope = null;
        switch(remoteScopeDescriptor.getTransport()) {
            case RMI:
                scope = new RmiExportingScope(remoteScopeDescriptor);
                break;
        }

        bindScope(remoteScopeDescriptor.getAnnotation(), scope);
        remoteScopes.put(remoteScopeDescriptor.getAnnotation(), remoteScopeDescriptor);
    }

    <T> void doRemotedBinding(RemoteBinding<T> remoteBinding) {
        //should register somewhere to hook this binding in
        ServiceExporters.export(binder(), remoteBinding, remoteScopes.get(remoteBinding.getRemoteScope()));

        //bind to guice as singleton
        bind(remoteBinding.getIface()).to(remoteBinding.getBinding()).in(Singleton.class);
    }
}
